// ExcelUtils.js

// TODO workBook2DataSet 使用 sheet_to_json 获取数据不灵活
// 遇到首行并非列定义的情况无法处理
// 需要增加 workBook2DataSet_StepByStep 函数来自己控制数据的读取

// V 0.0.2 - 2025-10-18 20:43:30
// 新增函数 workBook2DataSet
// 
// V 0.0.1 - 2025-10-18 18:55:17
// 首次创建 ExcelUtils 工具类

class ExcelUtils {
    /**
     * 读取 WorkBook 的内容并将其转换成类似 C# DataSet
     * 仅支持首行为列定义
     * @param {*} binaryData 
     * @returns 
     */
    static workBook2DataSet(binaryData) {
        if (!binaryData) {
            throw new Error("binaryData 不能为空");
        }
        if (typeof binaryData !== 'string' && !(binaryData instanceof ArrayBuffer) && !Array.isArray(binaryData)) {
            throw new Error("binaryData 必须是字符串、ArrayBuffer 或数组");
        }

        let workbook = XLSX.read(binaryData, {type: 'binary'});
        console.log('workbook:', workbook);

        let ds = {}; // C# 的 DataSet
        ds.Tables = []; // C# DataSet 的 Tables 属性

        let sheetIndex = 0;
        for (let sheet in workbook.Sheets) { // 遍历每张表读取
            if (workbook.Sheets.hasOwnProperty(sheet)) {
                let dt = {}; // C# 的 DataTable
                dt.TableName = workbook.SheetNames[sheetIndex];
                sheetIndex = sheetIndex + 1;

                dt.FromTo = workbook.Sheets[sheet]['!ref']; // 获取 sheet 数据的范围
                console.log("FromTo", dt.FromTo); // 返回结果例子 A1:B38

                if(dt.FromTo == undefined) {
                    ds.Tables.push(dt);
                    continue; // sheet 没有任何值
                }

                console.log("Worksheet", workbook.Sheets[sheet]);

                dt.Columns = [];
                // dt.Columns.push({ "prop": "__rowNum__", "label": "Excel行号" });
                
                //#region 读取列定义
                
                // 获取首行列定义思路
                // 根据 FromTo 的信息 定位首行的位置
                let startColumnName = dt.FromTo.match(/^[A-Z]+/)[0]; // 最小列名
                let endColumnName = dt.FromTo.match(/(?<=:)[A-Z]+/)[0]; // 最大列名
                
                let regexPattern = new RegExp( `^${startColumnName}[0-9]+` );
                console.log("regexPattern:", regexPattern);
                let columnRowNumber = dt.FromTo.match(regexPattern)[0].substring(startColumnName.length); // 列定义行号
                
                console.log("最小列名:", startColumnName);
                console.log("最大列名:", endColumnName);
                console.log("列定义行号", columnRowNumber);
                
                let startColumnIndex = ExcelUtils.toIndex(startColumnName);
                let endColumnIndex = ExcelUtils.toIndex(endColumnName);
                
                let emptyPropIndex = -1; // 遇到列头无法读取时, 统计 __EMPTY_Xx 的属性名

                for(let j = startColumnIndex; j < endColumnIndex + 1; j++)
                {
                    let currentColumnName = ExcelUtils.toExcelColumnName(j);
                    let cellLocation = `${currentColumnName}${columnRowNumber}`;
                    console.log("单元格定位: ", cellLocation);

                    let cell = workbook.Sheets[sheet][cellLocation];
                    console.log('单元格值: ', cell);

                    if(cell)
                    {
                        let column = {};
                        column.prop = cell.v;
                        column.label = cell.v;
                        dt.Columns.push(column);
                    }
                    //#region [暂时弃用] 让用户发现数据列缺失
                    // else
                    // {
                    //     // cell 为 undefine
                    //     emptyPropIndex = emptyPropIndex + 1;
                    //     let propName = emptyPropIndex == 0 ? `__EMPTY` : `__EMPTY_${emptyPropIndex}`;

                    //     let column = {};                        
                    //     column.prop = propName;
                    //     column.label = propName;
                    //     dt.Columns.push(column);
                    // }
                    //#endregion
                }

                //#endregion

                dt.Data = XLSX.utils.sheet_to_json(workbook.Sheets[sheet]); // 使用工具类将数据转成 Array (Json)
                // 执行 sheet_to_json 时 dt.Data 的属性会多出一个只读属性 __rowNum__ , 表示 Excel 的 RowIndex
                if(dt.Data[0].__rowNum__) // __rowNum__ 属性依赖 执行 sheet_to_json 函数, 需要先判断是否含有 __rowNum__ 属性
                {
                    dt.Data.forEach(row => row.ExcelRowNumber = row.__rowNum__ + 1); // 新增一个 ExcelRowNumber 属性, 表示数据在 Excel 的行号
                }                   
                                
                // XLSX.utils.sheet_to_json：输出JSON格式
                // 工具类除了 sheet_to_json 还有其他转换方法
                // XLSX.utils.sheet_to_csv：生成CSV格式
                // XLSX.utils.sheet_to_txt：生成纯文本格式
                // XLSX.utils.sheet_to_html：生成HTML格式

                ds.Tables.push(dt);
            }
        }
        return ds;
    }

    /**
     * 将 Excel 列名转换为零基索引（如 "A" -> 0, "AA" -> 26）
     * @param {string} excelColumnName - 大写字母组成的列名，例如 "A", "Z", "AA"
     * @returns {number} 零基索引
     */
    static toIndex(excelColumnName) {
        // 检查参数是否为大写字母组成的字符串
        if (!/^[A-Z]+$/.test(excelColumnName)) {
            throw new Error("invalid parameter");
        }
        let index = 0;
        let chars = excelColumnName.split(""); // 把字符串转换成字符数组
        for (let i = 0; i < chars.length; i++) {
            // 计算每个字符对应的数值，A 对应 1，B 对应 2，以此类推
            let value = chars[i].charCodeAt(0) - "A".charCodeAt(0) + 1;
            // 根据字符在字符串中的位置，计算权重，最左边的字符权重最大，为 26 的 n-1 次方，其中 n 为字符串长度
            let factor = Math.pow(26, chars.length - i - 1);
            // 累加每个字符的数值乘以权重
            index += value * factor;
        }
        // 返回结果减一，因为索引从零开始
        return index - 1;
    }

    /**
     * 将零基索引转换为 Excel 列名（如 0 -> "A", 26 -> "AA"）
     * @param {number} index - 零基索引
     * @returns {string} Excel 列名
     */
    static toExcelColumnName(index) {
        if (index < 0 || !Number.isInteger(index)) {
            throw new Error("Invalid parameter: index must be a non-negative integer.");
        }
        const chars = []; // 创建一个空数组，用来存放字符
        do {
            if (chars.length > 0) 
            {
                index--; // 如果数组不为空，索引减一，因为从 A 开始而不是从 AA 开始
            }
            // 计算索引对应的字符，A 对应 0，B 对应 1，以此类推
            const charCode = (index % 26) + "A".charCodeAt(0);
            // 把字符插入到数组的开头
            chars.unshift(String.fromCharCode(charCode));
            // 计算下一个循环的索引，相当于把索引除以 26，然后取整
            index = Math.floor(index / 26);
        } while (index > 0); // 当索引大于零时，继续循环

        // 把数组中的字符连接成一个字符串，并返回
        return chars.join("");
    }

    /**
     * 将对象数组转换为 XLSX Sheet 对象（自动提取表头并填充数据）
     * @param {Array<Object>} arr - 对象数组
     * @returns {XLSX.WorkSheet} XLSX 工作表对象
     */
    static arrayToSheet(arr) {
        if (!Array.isArray(arr)) {
            throw new Error("传入的参数arr必须是Array。");
        }

        if (arr.length === 0) {
            throw new Error("数组长度为零。");
        }
            // 创建一个 Set 来保存所有的 header
            const headersSet = new Set();
            
            // 收集所有的 header
            arr.forEach(function(row) {
                Object.keys(row).forEach(key => headersSet.add(key));
            });

            // 创建一个包含所有属性名的头行
            const headers = Array.from(headersSet);

            // 创建一个二维数组，用于存放所有的行数据
            let data = [headers];
            
            // 遍历每条记录，并添加到data中
            arr.forEach(function(row) {
                let rowData = headers.map(function(header) {
                    return row[header] || null; // 如果不存在该字段，则用 null 代替
                });
                data.push(rowData);
            });

            let r = XLSX.utils.aoa_to_sheet(data);
            return r;
    }
}

// 改为挂载到 window（全局作用域），而不是 export
// export default ExcelUtils;
window.ExcelUtils = ExcelUtils;